﻿using System.ComponentModel;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.PropertyGrid.ViewModels;

namespace Avalonia.PropertyGrid.Controls;

/// <summary>
/// Interface ICellEditFactory
/// </summary>
public interface ICellEditFactory
{
    /// <summary>
    /// Gets the import priority.
    /// The larger the value, the earlier the object will be processed
    /// </summary>
    /// <value>The import priority.</value>
    [Browsable(false)]
    int ImportPriority { get; }

    /// <summary>
    /// Gets the collection.
    /// </summary>
    /// <value>The collection.</value>
    [Browsable(false)]
    ICellEditFactoryCollection? Collection { get; internal set; }

    /// <summary>
    /// Check available for target
    /// </summary>
    /// <param name="accessToken">The access token.</param>
    /// <returns><c>true</c> if success, <c>false</c> otherwise.</returns>
    bool Accept(object accessToken);

    /// <summary>
    /// Clones this instance.
    /// </summary>
    /// <returns>ICellEditFactory.</returns>
    ICellEditFactory? Clone();

    /// <summary>
    /// Handles the new property.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <returns>Control.</returns>
    Control? HandleNewProperty(PropertyCellContext context);

    /// <summary>
    /// Handles the property changed.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <returns><c>true</c> if success, <c>false</c> otherwise.</returns>
    bool HandlePropertyChanged(PropertyCellContext context);

    /// <summary>
    /// Handles the propagate visibility.
    /// </summary>
    /// <param name="target">The target.</param>
    /// <param name="context">The context.</param>
    /// <param name="filterContext">The filter context.</param>
    /// <param name="filterText">The filter text.</param>
    /// <param name="filterMatchesParentCategory">Indicates whether the filter matches the parent category.</param>
    /// <returns>System.Nullable&lt;PropertyVisibility&gt;.</returns>
    PropertyVisibility? HandlePropagateVisibility(
        object? target, 
        PropertyCellContext context, 
        IPropertyGridFilterContext filterContext,
        string? filterText = null,
        bool filterMatchesParentCategory = false);

    /// <summary>
    /// Handles readonly flag changed
    /// </summary>
    /// <param name="control">control.</param>
    /// <param name="readOnly">readonly flag</param>
    /// <returns>Control.</returns>
    void HandleReadOnlyStateChanged(Control control, bool readOnly);
        
    /// <summary>
    /// use internal framework to set property value, include command executing...
    /// </summary>
    /// <param name="context"></param>
    /// <param name="value"></param>
    void SetPropertyValue(PropertyCellContext context, object? value);

    /// <summary>
    /// Gets the property value.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <returns>System.Nullable&lt;System.Object&gt;.</returns>
    object? GetPropertyValue(PropertyCellContext context);
}

/// <summary>
/// Class PropertyCellContext.
/// </summary>
public class PropertyCellContext
{
    /// <summary>
    /// The root
    /// </summary>
    public readonly IPropertyGrid? Root;

    /// <summary>
    /// The owner
    /// </summary>
    public readonly IPropertyGrid Owner;

    /// <summary>
    /// The target
    /// </summary>
    public readonly object Target;

    /// <summary>
    /// The property
    /// </summary>
    public readonly PropertyDescriptor Property;

    /// <summary>
    /// Gets the display name.
    /// </summary>
    /// <value>The display name.</value>
    public string DisplayName => Property.DisplayName;

    /// <summary>
    /// The cell edit
    /// </summary>
    public Control? CellEdit { get; set; }

    /// <summary>
    /// Gets or sets the factory.
    /// </summary>
    /// <value>The factory.</value>
    public ICellEditFactory? Factory { get; set; }

    /// <summary>
    /// The parent
    /// </summary>
    public readonly PropertyCellContext? ParentContext;

    /// <summary>
    /// If this property should be readonly
    /// </summary>
    public bool IsReadOnly => Property.IsReadOnly || Owner.IsReadOnly || ParentContext is { IsReadOnly: true };

    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <returns>System.Object.</returns>
    public object? GetValue() => Property.GetValue(Target);

    /// <summary>
    /// Gets the cell edit factory collection.
    /// </summary>
    /// <returns>ICellEditFactoryCollection.</returns>
    public ICellEditFactoryCollection GetCellEditFactoryCollection()
    {
        Debug.Assert(Root != null);

        return Root.GetCellEditFactoryCollection();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PropertyCellContext" /> class.
    /// </summary>
    /// <param name="parentContext">The parent context.</param>
    /// <param name="root">The root.</param>
    /// <param name="owner">The owner.</param>
    /// <param name="target">The target.</param>
    /// <param name="property">The property.</param>
    /// <param name="cellEdit">The cell edit.</param>
    public PropertyCellContext(PropertyCellContext? parentContext, IPropertyGrid root, IPropertyGrid owner, object target, PropertyDescriptor property, Control? cellEdit = null)
    {
        ParentContext = parentContext;
        Root = root;
        Owner = owner;
        Target = target;
        Property = property;

        CellEdit = cellEdit;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PropertyCellContext" /> class.
    /// </summary>
    /// <param name="parentContext">The parent context.</param>
    /// <param name="target">The target.</param>
    /// <param name="property">The property.</param>
    public PropertyCellContext(PropertyCellContext parentContext, object target, PropertyDescriptor property)
    {
        ParentContext = parentContext;
        Root = parentContext.Root;
        Owner = parentContext.Owner;
        Target = target;
        Property = property;
    }

    /// <summary>
    /// Returns a <see cref="string" /> that represents this instance.
    /// </summary>
    /// <returns>A <see cref="string" /> that represents this instance.</returns>
    public override string ToString() => DisplayName;
}